今天要介紹React的另一個Hook-useReducer,useReducer和useState一樣都是用於狀態管理的Hook,在同個專案或是component裡useState和useReducer可以搭配交互使用沒有影響。在使用的選擇上,如果狀態的資料結構比較複雜且應用本身會對於狀態做不同的操作,像是新增、修改和刪除等,不是單一的狀態存取,就可以考慮使用useReducer,幫助我們寫出更容易理解與debug的程式。
主要可以分為3個區塊
state:狀態
dispatch:派送行為
reducer:執行操作
假設我們要做一個有新增和刪除的todo list
定義reducer
reducer會帶有原本state和傳入的action兩個參數,然後action會帶有type(動作種類)和payload(更新狀態需要的內容)
function tasksReducer(state, action) {
switch(action.type) {
case 'add': {
return [...state, action.payload]
}
case 'delete': {
return state.filter((task) => task.id !== action.payload.id)
}
}
}
宣告useReducer
帶入定義好的reducer和初始狀態值,會回傳一個帶有狀態和dispatch的陣列
const [tasks, taskDispatch] = useReducer(tasksReducer, initialTasks)
使用dispatch
// 新增
taskDispatch({type: 'add', payload: {id: createId(), text: todo, done: false}})
// 有時候我也會將action包成function使用,會更簡潔一點
function addAction(todo) {
return {type: 'add', payload: {id: createId(), text: todo, done: false}}
}
taskDispatch(addAction(todo));
//刪除
taskDispatch({type: 'delete', payload: task})
// 有時候我也會將action包成function使用,會更簡潔一點
function delAction(todo) {
return {type: 'delete', payload: todo}
}
taskDispatch(delAction(task))
完整範例
import { useReducer, useState } from 'react'
function App() {
const [tasks, taskDispatch] = useReducer(tasksReducer, initialTasks)
const [todo, setTodo] = useState('');
console.log(tasks);
return (
<>
<ul>
{
tasks.map((task) => {
return <li key={task.id}>
{task.text}
<button onClick={() => taskDispatch(delAction(task))}>刪除</button>
</li>
})
}
<input type="text" value={todo} onChange={(e) => setTodo(e.target.value)}/>
<button onClick={() => {
taskDispatch(addAction(todo));
}}>新增</button>
</ul>
</>
)
}
function addAction(todo) {
return {type: 'add', payload: {id: createId(), text: todo, done: false}}
}
function delAction(todo) {
return {type: 'delete', payload: todo}
}
function createId() {
return new Date().getTime();
}
function tasksReducer(state, action) {
switch(action.type) {
case 'add': {
return [...state, action.payload]
}
case 'delete': {
return state.filter((task) => task.id !== action.payload.id)
}
default: {
return state
}
}
}
const initialTasks = [
{id: 0, text: '吃飯', done: true},
{id: 1, text: '睡覺', done: false},
{id: 2, text: '打電動', done: false},
];
export default App
useReducer這個名稱取的很像是我們陣列裡用的reduce函式,但是不只名字很像,實際上在做的事情也差不多,所以React才會以reducer來命名。
const initialTasks = [
{id: 0, text: '吃飯', done: true},
{id: 1, text: '睡覺', done: false},
{id: 2, text: '打電動', done: false},
];
function tasksReducer(state, action) {
switch(action.type) {
case 'add': {
return [...state, action.payload]
}
case 'delete': {
return state.filter((task) => task.id !== action.payload.id)
}
}
}
// 類似我們js reduce的用法,像是reduce compose的用法
initialTasks.reduce(tasksReducer, initialTasks)
官方文件有提到可以使用**Immer**直接對於狀態進行操作,但是本身其實也蠻習慣會使用Spread Operator[…state],要直接修改物件或是陣列我一整個覺得不習慣,有沒有人也是這樣的,歡迎分享。
https://react.dev/learn/extracting-state-logic-into-a-reducer